{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum Teleporation\n",
    "\n",
    "\n",
    "Below we shall study an interesting quantum phenomena - teleportation.\n",
    "\n",
    "Let us start by implementing the circuit shown below and discuss its implications later. We normally assume that the input qubits are initialised to the $\\ket{0}$ state unless specified otherwise. In this case however, the circuit diagram depicts the 0<sup>th</sup> qubit to be initialised in an arbitary quantum state, $\\ket{\\psi} = \\alpha\\ket{0} + \\beta\\ket{1}$, and the 1<sup>st</sup> and 2<sup>nd</sup> qubit to be in the state $\\ket{\\beta_{00}} = \\tfrac{1}{\\sqrt{2}} [\\ket{00} + \\ket{11}]$.\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![tene.png](./images/teleportation.png)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cudaq\n",
    "import numpy as np\n",
    "\n",
    "cudaq.set_target('qpp-cpu')\n",
    "cudaq.set_random_seed(23)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# We now replicate the circuit shown in the diagram above and step through each intermediate stage from psi0 to psi4.\n",
    "\n",
    "\n",
    "@cudaq.kernel\n",
    "def teleportation():\n",
    "\n",
    "    # Initialize a 3 qubit quantum circuit\n",
    "    qubits = cudaq.qvector(3)\n",
    "\n",
    "    # Psi0 - random quantum state, psi, on qubit 0.\n",
    "    rx(3.14, qubits[0])\n",
    "    ry(2.71, qubits[0])\n",
    "    rz(6.62, qubits[0])\n",
    "\n",
    "    # Psi0 - create a maximally entangled state on qubits 1 and 2.\n",
    "    h(qubits[1])\n",
    "    cx(qubits[1], qubits[2])\n",
    "\n",
    "\n",
    "# Let us save the statevector of the circuit so far for later use.\n",
    "psi_0 = cudaq.get_state(teleportation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Now we continue with the rest of the circuit\n",
    "\n",
    "\n",
    "@cudaq.kernel\n",
    "def teleportation():\n",
    "\n",
    "    # Initialize a 3 qubit quantum circuit\n",
    "    qubits = cudaq.qvector(3)\n",
    "\n",
    "    # Psi0 - random quantum state, psi, on qubit 0.\n",
    "    rx(3.14, qubits[0])\n",
    "    ry(2.71, qubits[0])\n",
    "    rz(6.62, qubits[0])\n",
    "\n",
    "    # Psi0 - create a maximally entangled state on qubits 1 and 2.\n",
    "    h(qubits[1])\n",
    "    cx(qubits[1], qubits[2])\n",
    "\n",
    "    # Psi1\n",
    "    cx(qubits[0], qubits[1])\n",
    "\n",
    "    # Psi2\n",
    "    h(qubits[0])\n",
    "\n",
    "    # Psi3 - measure qubits 0 and 1 and store it in variables m1 and m2\n",
    "    m1 = mz(qubits[0])\n",
    "    m2 = mz(qubits[1])\n",
    "\n",
    "    # Psi4 - apply conditioned pauli operators dependent on the measurement result of qubits 0 and 1.\n",
    "    if m1 == 1:\n",
    "        z(qubits[2])\n",
    "\n",
    "    if m2 == 1:\n",
    "        x(qubits[2])\n",
    "\n",
    "\n",
    "psi_4 = cudaq.get_state(teleportation)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The state of this 3 qubit system is described by a $2^3 = 8$ dimensional vector. Sometimes we would like to extract the state of a single qubit from a multipartite state. We can use the partial trace operation defined in the function below which allows us to translate our statevectors to the density matrix representation to trace out subsystems. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Function to be used below to calculate the partial trace yielding a density matrix.\n",
    "\n",
    "\n",
    "def partial_trace(state_vector, trace_systems):\n",
    "    \"\"\"\n",
    "    Partial trace of multi-particle quantum state. \n",
    "\n",
    "    Arguments:\n",
    "        state_vector: complex vector  of size 2**n\n",
    "        trace_systems (list(int)): a list of subsystems (starting from 0) to trace over. \n",
    "        dimensions (list(int)): a list of the dimensions of the subsystems.\n",
    "  \n",
    "    Returns:\n",
    "        ndarray: A density matrix with the appropriate subsystems traced over.\n",
    "    \"\"\"\n",
    "\n",
    "    n_qubits = int(np.log2(state_vector.shape[0]))\n",
    "\n",
    "    dimensions = [2 for i in range(n_qubits)]\n",
    "\n",
    "    trace_systems = len(dimensions) - 1 - np.array(trace_systems)\n",
    "\n",
    "    rho = state_vector.reshape(dimensions)\n",
    "    rho = np.tensordot(rho, rho.conj(), axes=(trace_systems, trace_systems))\n",
    "    d = int(np.sqrt(np.prod(rho.shape)))\n",
    "\n",
    "    return rho.reshape(d, d)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|qubit_0> == |qubit_2>?  True\n"
     ]
    }
   ],
   "source": [
    "# Trace out qubits 1 and 2 leaving us with qubit 0.\n",
    "state_of_q0 = partial_trace(state_vector=np.array(psi_0), trace_systems=[1, 2])\n",
    "\n",
    "# Trace out qubits 0 and 1 leaving us with qubit 2.\n",
    "state_of_q2 = partial_trace(state_vector=np.array(psi_4), trace_systems=[0, 1])\n",
    "\n",
    "print(f\"|qubit_0> == |qubit_2>?  {np.allclose(state_of_q0, state_of_q2)}\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let us discuss what has happened. \n",
    "\n",
    "We started with a 3 qubit quantum circuit with the 0<sup>th</sup> qubit in $\\ket{\\psi}$ and the 2<sup>nd</sup> qubit being a portion of the maximally entangled bell state $\\ket{\\beta_{00}}$.\n",
    "\n",
    "If we look closely at the code cell above, the output state on the 2<sup>nd</sup> qubit, `state_of_q2`, is the same as the random input state, $\\ket{\\psi}$, on the 0<sup>th</sup> qubit, `state_of_q0`. \n",
    "\n",
    "We have **teleported** a quantum state from one qubit to another. There is nothing to restrict how close qubits 0 and 2 have to be since they have no entangling operations between them. They can be placed in different labs or infinitely far apart. \n",
    "\n",
    "Moreover, we still obey the no-cloning theorem since the initial state $\\ket{\\psi}$ no longer exists on the 0<sup>th</sup> qubit and hence we only have 1 copy of it as shown below \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Using the final statevector, psi4, we show that psi has been teleported and only 1 copy of psi is preserved.\n",
    "\n",
    "# Trace out qubits 1 and 2 leaving us with qubit 0\n",
    "state_of_q0 = partial_trace(state_vector=np.array(psi_4), trace_systems=[1, 2])\n",
    "\n",
    "# Trace out qubits 0 and 1 leaving us with qubit 2\n",
    "state_of_q2 = partial_trace(state_vector=np.array(psi_4), trace_systems=[0, 1])\n",
    "\n",
    "np.allclose(state_of_q0, state_of_q2)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "None of this violates the universal limit of information travelling faster than the speed of light. Notice how we have conditioned operations meaning we have to communicate classical information via classical channels which adhere to this limit."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Teleportation explained\n",
    "\n",
    "We have stepped through the circuit above using code. We will now do the same using mathematics to enhance our understanding. \n",
    "\n",
    "\n",
    "We start by gathering experimentalists in a lab and performing the following\n",
    "\n",
    "$$\n",
    "\\begin{aligned} \n",
    "CX_{01}H_{0}\\ket{00} &= CX_{01}\\tfrac{1}{\\sqrt{2}}(\\ket{0}+\\ket{1})\\ket{0} \\\\\n",
    "                        &= CX_{01}\\tfrac{1}{\\sqrt{2}}(\\ket{00}+\\ket{10}) \\\\\n",
    "                        &= \\tfrac{1}{\\sqrt{2}}(\\ket{00}+\\ket{11}) \\\\\n",
    "                        &= \\ket{\\beta_{00}}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "to create an entangled bell pair. We then hand each qubit in $\\ket{\\beta_{00}}$ to two parties, namely Alice and Bob for safekeeping. Many moons later, Alice comes in posession of an arbitary quantum state $\\ket{\\psi} = \\alpha\\ket{0} + \\beta\\ket{1}$ which she would like to send to Bob who is many miles away. This entails communicating values of $\\alpha$ and $\\beta$. However, measurement would lead to state collapse and only yield one bit of information, 0 or a 1.\n",
    "\n",
    "Referring to circuit in the figure above, the 0<sup>th</sup> and the 1<sup>st</sup> qubits are in posession of Alice and the 2<sup>nd</sup> qubit is with Bob. The 1<sup>st</sup> and 2<sup>nd</sup> qubits have been entangled but are now seperated in distance. Let us now step through the circuit to describe its evolution. Our input state is \n",
    "\n",
    "$$ \n",
    "\\begin{aligned} \n",
    "\\ket{\\psi_{0}} &=  \\ket{\\psi} \\ket{\\beta_{00}}  = (\\alpha\\ket{0} + \\beta\\ket{1})(\\tfrac{1}{\\sqrt{2}} (\\ket{00} + \\ket{11}) \\\\\n",
    "&= \\tfrac{1}{\\sqrt{2}}[\\alpha\\ket{0}(\\ket{00} + \\ket{11}) +\\beta\\ket{1}(\\ket{00} + \\ket{11})]\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Alice then performs a CX gate between the qubits she posses i.e. qubit 0 and 1 \n",
    "\n",
    "$$ \n",
    "\\begin{aligned} \n",
    "\\ket{\\psi_{1}} &= CX_{01}\\ket{\\psi_{0}} \\\\\n",
    "                &= \\tfrac{1}{\\sqrt{2}}[\\alpha\\ket{0}(\\ket{00} + \\ket{11}) +\\beta\\ket{1}(\\ket{10} + \\ket{01})]\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "which is then followed by Alice performing a Hadamard on the 0<sup>th</sup> qubit\n",
    "\n",
    "$$ \n",
    "\\begin{aligned} \n",
    "\\ket{\\psi_{2}} &= H_{0}\\ket{\\psi_{1}} \\\\\n",
    "                &= \\tfrac{1}{{2}}[\\alpha(\\ket{0}+\\ket{1})(\\ket{00} + \\ket{11}) +\\beta(\\ket{0}-\\ket{1})(\\ket{10} + \\ket{01})].\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "If we expand all the inner brackets\n",
    "\n",
    "$$\n",
    "\\begin{aligned} \n",
    "\\ket{\\psi_{2}} &= \\tfrac{1}{{2}}[(\\alpha\\ket{0} + \\alpha\\ket{1})(\\ket{00} + \\ket{11}) + (\\beta\\ket{0} - \\beta\\ket{1})(\\ket{10} + \\ket{01})] \\\\\n",
    "                &= \\tfrac{1}{{2}}[\\alpha\\ket{000}+\\alpha\\ket{011}  +\\alpha\\ket{100}  +\\alpha\\ket{111} +\\beta\\ket{010}+\\beta\\ket{001}-\\beta\\ket{110}-\\beta\\ket{101}]\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "and then collect like terms whilst maintaining qubit ordering, we yield \n",
    "\n",
    "\n",
    "$$ \n",
    "\\begin{aligned} \n",
    "\\ket{\\psi_{2}} &= \\tfrac{1}{{2}}[  \\ket{00}(\\alpha\\ket{0}+\\beta\\ket{1}) + \\ket{01}(\\alpha\\ket{1}+\\beta\\ket{0}) +\n",
    " \\ket{10}(\\alpha\\ket{0}-\\beta\\ket{1}) + \\ket{11}(\\alpha\\ket{1}+\\beta\\ket{0})                              ]\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "where the qubit ordering notation states that the left most qubit is the 0<sup>th</sup> qubit. This expression naturally breaks down into 4 terms where each term has all 3 qubits and the 4 terms represent all the possibilities they can be in after they have been evolved.  \n",
    "\n",
    "We now ask our friend Alice to measure her qubits. Quantum mechanically we know that upon measurement the possibilities of the 3 qubit system will collapse regardless of the distance between them and the unmeasured outcomes will therefore be deterministic. If her measurement result yields a 00, the first term in $\\ket{\\psi_{2}}$ tells us that Bob's qubit will be in the state $\\alpha\\ket{0}+\\beta\\ket{1}$ which is the the original state, $\\ket{\\psi}$, we wanted to teleport. Alice has other potential measurement outcomes all of which are summarised in the figure below \n",
    "\n",
    "\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "\n",
    "\n",
    "| Alices' measurement        | Bobs' state           | \n",
    "| ---------------------------|:---------------------:| \n",
    "|  $$00$$ | $$  \\ket{\\psi_3(00)} = \\alpha\\ket{0} + \\beta\\ket{1} $$ | \n",
    "|  $$01$$ | $$ \\ket{\\psi_3(01)} =  \\alpha\\ket{1} + \\beta\\ket{0} $$ | \n",
    "|  $$10$$ | $$ \\ket{\\psi_3(10)} = \\alpha\\ket{0} - \\beta\\ket{1} $$ | \n",
    "|  $$11$$ | $$ \\ket{\\psi_3(11)} = \\alpha\\ket{1} - \\beta\\ket{0} $$ | \n",
    "\n",
    "\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A quick glance at the figure above shows us that Bob's qubit is nearly in the state $\\ket{\\psi}$ which is what we are after pending some minor corrections. The circuit diagram depicts conditioned gates that are applied depending on Alice's measurement result fulfilling the minor corrections required to complete the teleportation protocol. The final gate operations are summarised below "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$ \n",
    "\\begin{aligned} \n",
    "M1M2 = 00, \\ket{\\psi_4} = Z^{0}X^{0}\\ket{\\psi_3(00)} = \\alpha\\ket{0} + \\beta\\ket{1} = \\ket{\\psi} \\\\\n",
    "M1M2 = 01, \\ket{\\psi_4} = Z^{0}X^{1}\\ket{\\psi_3(01)} = \\alpha\\ket{0} + \\beta\\ket{1} = \\ket{\\psi} \\\\\n",
    "M1M2 = 10, \\ket{\\psi_4} = Z^{1}X^{0}\\ket{\\psi_3(10)} = \\alpha\\ket{0} + \\beta\\ket{1} = \\ket{\\psi} \\\\\n",
    "M1M2 = 11, \\ket{\\psi_4} = Z^{1}X^{1}\\ket{\\psi_3(11)} = \\alpha\\ket{0} + \\beta\\ket{1} = \\ket{\\psi} \n",
    "\\end{aligned}\n",
    "$$"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It is clear to see that in all instances, we recover $\\ket{\\psi}$ on Bob's qubit thus fulfilling our ambition of teleporting an unknown quantum state between 2 parties. \n",
    "\n",
    "It is important to note that quantum teleportation does not allow communication faster than the speed of light. The state $\\ket{\\psi}$ does not instantly appear with Bob. Alice has to use a classical communication channel which is bound by the speed of classical physics to communicate her measurement results to Bob so that he can make the minor corrections required. \n",
    "\n",
    "Moreover teleportation does not violate the no-cloning theorem. The protocol does not allow us to create a copy of $\\ket{\\psi}$ leaving us with $\\ket{\\psi\\psi}$ but rather transmits $\\ket{\\psi}$ from Alice to Bob. "
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The images used in this notebook are courtesey of the Quantum Computation and Quantum Information textbook by Nielsen & Chuang.\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
